var roomMgr = require("./roommgr");
var userMgr = require("./usermgr");
var mjutils = require('./mjutils');
var db = require("../utils/db");
var crypto = require("../utils/crypto");

var games = {};

var TYPE_GANG = 1;
var TYPE_PIAO = 2;
var TYPE_GANG_KAI = 3;
var TYPE_BAOTOU = 4;

var ACTION_CHUPAI = 1;
var ACTION_MOPAI = 2;
var ACTION_PENG = 3;
var ACTION_GANG = 4;
var ACTION_HU = 5;

var gameSeatsOfUsers = {};

function in_array(search, array) {
	for (var i in array) {
		if (array[i] == search) {
			return true;
		}
	}
	return false;
}

function index_of(arr, item) {
	for (var i = 0; i <= arr.length; i++) {
		if (arr[i] === item) {
			return i;
		}
	}
}

function inMJUtils(holds) {
	for (var i = 0; i < holds.length; i++) {
		var hold = parseInt(holds[i]);
		if (hold >= 0 && hold < 9) {
			//筒
			holds[i] = hold + 1;
		} else if (hold >= 9 && hold < 18) {
			//条
			holds[i] = hold + 2;
		} else if (hold >= 18 && hold < 27) {
			//万
			holds[i] = hold + 3;
		} else if (hold >= 27 && hold < 34) {
			//风
			holds[i] = hold + 4;
		} else {
			console.log("error hold")
		}
	}

	return holds;
}

function outMJUtils(holds) {
	for (var i = 0; i < holds.length; i++) {
		var hold = parseInt(holds[i]);
		if (hold > 0 && hold < 10) {
			//筒
			holds[i] = hold - 1;
		} else if (hold > 10 && hold < 20) {
			//条
			holds[i] = hold - 2;
		} else if (hold > 20 && hold < 30) {
			//万
			holds[i] = hold - 3;
		} else if (hold > 30 && hold < 38) {
			//风
			holds[i] = hold - 4;
		} else {
			console.log("error hold")
		}
	}

	return holds;
}

function shuffle(game) {
	var mahjongs = game.mahjongs;
	var renshu = game.conf.renshu;

	//筒 0 ~ 8 表示筒子
	var index = 0;
	for (var i = 0; i < 9; ++i) {
		for (var c = 0; c < 4; ++c) {
			mahjongs[index] = i;
			index++;
		}
	}

	//条 9 ~ 17表示条子
	for (var i = 9; i < 18; ++i) {
		for (var c = 0; c < 4; ++c) {
			mahjongs[index] = i;
			index++;
		}
	}

	if (renshu > 2) {
		//万 18 ~ 26表示万
		for (var i = 18; i < 27; ++i) {
			for (var c = 0; c < 4; ++c) {
				mahjongs[index] = i;
				index++;
			}
		}
	}

	//风牌
	for (var i = 27; i < 34; ++i) {
		for (var c = 0; c < 4; ++c) {
			mahjongs[index] = i;
			index++;
		}
	}

	for (var i = 0; i < mahjongs.length; ++i) {
		var lastIndex = mahjongs.length - 1 - i;
		var index = Math.floor(Math.random() * lastIndex);
		var t = mahjongs[index];
		mahjongs[index] = mahjongs[lastIndex];
		mahjongs[lastIndex] = t;
	}

	//var mj = [ 33, 9, 33, 9, 0, 11, 0, 4, 0, 2, 1, 14, 1, 8, 1,
	//	1, 17, 27, 17, 8, 16, 12, 16, 6, 16, 17, 33, 27, 16,
	//	2, 32, 29, 12, 17, 33, 27, 28, 13, 5, 15, 14, 4, 7,
	//	30, 8, 31, 2, 31, 15, 7, 10, 32, 31, 5, 5, 10, 29,
	//	32, 14, 10, 32, 4, 6, 12, 15, 3, 6, 14, 13, 10, 13,
	//	11, 28, 7, 5, 30, 8, 6, 28, 29, 13, 11, 3, 2, 7,
	//	9, 15, 28, 12, 27, 0, 31, 30, 3, 29, 9, 3, 30, 11, 4 ];
	//
	//for (var i = 0; i < mahjongs.length; ++i) {
	//	mahjongs[i] = mj[i];
	//}
}

function mopai(game, seatIndex) {
	if (game.currentIndex == game.mahjongs.length) {
		return -1;
	}
	var data = game.gameSeats[seatIndex];
	var mahjongs = data.holds;
	var pai = game.mahjongs[game.currentIndex];
	mahjongs.push(pai);

	//统计牌的数目 ，用于快速判定（空间换时间）
	var c = data.countMap[pai];
	if (c == null) {
		c = 0;
	}
	data.countMap[pai] = c + 1;
	game.currentIndex++;
	return pai;
}

function deal(game) {
	//强制清0
	game.currentIndex = 0;

	//每人13张 一共 13*4 ＝ 52张 庄家多一张 53张
	var renshu = game.conf.renshu;
	var seatIndex = game.button;
	for (var i = 0; i < renshu * 13; ++i) {
		var mahjongs = game.gameSeats[seatIndex].holds;
		if (mahjongs == null) {
			mahjongs = [];
			game.gameSeats[seatIndex].holds = mahjongs;
		}
		mopai(game, seatIndex);
		seatIndex++;
		seatIndex %= renshu;
	}

	//庄家多摸最后一张
	mopai(game, game.button);
	//当前轮设置为庄家
	game.turn = game.button;
}

//检查是否可以碰
function checkCanPeng(game, seatData, targetPai) {
	// 如果是发财，直接忽略
	if (targetPai == 33) {
		return;
	}

	var count = seatData.countMap[targetPai];
	if (count != null && count >= 2) {
		seatData.canPeng = true;
	}
}

//检查是否可以点杠
function checkCanDianGang(game, seatData, targetPai) {
	// 如果是发财，直接忽略
	if (targetPai == 33) {
		return;
	}
	//检查玩家手上的牌
	//如果没有牌了，则不能再杠
	if (game.mahjongs.length <= game.currentIndex) {
		return;
	}
	var count = seatData.countMap[targetPai];
	if (count != null && count >= 3) {
		seatData.canGang = true;
		seatData.gangPai.push(targetPai);
	}
}

//检查是否可以暗杠
function checkCanAnGang(game, seatData) {
	//如果没有牌了，则不能再杠
	if (game.mahjongs.length <= game.currentIndex) {
		return;
	}

	for (var key in seatData.countMap) {
		var pai = parseInt(key);
		var c = seatData.countMap[key];
		if (c != null && c == 4 && pai != 33) {
			seatData.canGang = true;
			seatData.gangPai.push(pai);
		}
	}
}

//检查是否可以弯杠(自己摸起来的时候)
function checkCanWanGang(game, seatData) {
	//如果没有牌了，则不能再杠
	if (game.mahjongs.length <= game.currentIndex) {
		return;
	}

	//从碰过的牌中选
	for (var i = 0; i < seatData.pengs.length; ++i) {
		var pai = seatData.pengs[i];
		if (seatData.countMap[pai] == 1 && pai != 33) {
			seatData.canGang = true;
			seatData.gangPai.push(pai);
		}
	}
}

//检查能不能胡牌
function checkCanHu(game, seatData, targetPai) {
	seatData.canHu = false;
	seatData.pattern = "normal";
	var tingList = seatData.tingList;
	var patternList = seatData.patternList;
	var index = tingList.indexOf(targetPai);
	if (index > -1) {
		seatData.canHu = true;
		seatData.pattern = patternList[index];
		return true;
	}

	return false
}

function clearAllOptions(game, seatData) {
	var fnClear = function (sd) {
		sd.canPeng = false;
		sd.canGang = false;
		sd.canHu = false;
		sd.gangPai = [];
	};

	if (seatData) {
		fnClear(seatData);
	}
	else {
		for (var i = 0; i < game.gameSeats.length; ++i) {
			fnClear(game.gameSeats[i]);
		}
	}
}

//检查听牌
function checkCanTingPai(game, seatData) {
	seatData.tingList = [];

	var holds = JSON.parse(JSON.stringify(seatData.holds));

	if (holds.length <= 0) {
		return;
	}

	holds = inMJUtils(holds);
	var result = mjutils.canHu(holds);
	seatData.tingList = outMJUtils(result.hu);
	seatData.patternList = result.pattern;
	seatData.canBaoTou = result.baotou;
}

function hasOperations(seatData) {
	if (seatData.canGang || seatData.canPeng || seatData.canHu) {
		return true;
	}
	return false;
}

function sendOperations(game, seatData, pai) {
	if (hasOperations(seatData)) {
		if (pai == -1) {
			pai = seatData.holds[seatData.holds.length - 1];
		}

		var data = {
			pai: pai,
			hu: seatData.canHu,
			ispiao: seatData.isPiaoCai,
			baotou: seatData.canBaoTou,
			peng: seatData.canPeng,
			gang: seatData.canGang,
			gangpai: seatData.gangPai
		};

		//如果可以有操作，则进行操作
		userMgr.sendMsg(seatData.userId, 'game_action_push', data);

		data.si = seatData.seatIndex;
	}
	else {
		userMgr.sendMsg(seatData.userId, 'game_action_push');
	}
}

function moveToNextUser(game, nextSeat) {
	var renshu = game.conf.renshu;
	//找到下一个没有和牌的玩家
	if (nextSeat == null) {
		game.turn++;
		game.turn %= renshu;
	}
	else {
		game.turn = nextSeat;
	}
}

//检查摸牌
function doUserMoPai(game) {
	var renshu = game.conf.renshu;

	game.chuPai = -1;
	var turnSeat = game.gameSeats[game.turn];
	var pai = mopai(game, game.turn);

	//牌摸完了，结束
	var numOfMJ = game.mahjongs.length - game.currentIndex;

	if (renshu > 2) {
		if (numOfMJ == 14 || pai == -1) {
			game.state = "huang";
			doGameOver(game, renshu, turnSeat.userId);
			return;
		} else {
			userMgr.broacastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
		}
	} else {
		if (pai == -1) {
			game.state = "huang";
			doGameOver(game, renshu, turnSeat.userId);
			return;
		} else {
			userMgr.broacastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
		}
	}

	recordGameAction(game, game.turn, ACTION_MOPAI, pai);

	//通知前端新摸的牌
	userMgr.sendMsg(turnSeat.userId, 'game_mopai_push', pai);
	//检查是否可以暗杠，弯杠
	checkCanAnGang(game, turnSeat);

	//如果摸起来的牌可以杠，才检查弯杠
	if (turnSeat.holds[turnSeat.holds.length - 1] == pai) {
		checkCanWanGang(game, turnSeat, pai);
	}

	//根据配置里面是有财神无番可胡，有财神1番可胡
	var noNeedCheckHu = true;
	if (game.conf.caishen > 1) {
		//有财神1番可胡
		var holds = turnSeat.holds.concat();
		var hasCaiShen = in_array(33, holds);
		var fanLen = turnSeat.fanRecord.length;
		holds.pop();
		holds = inMJUtils(holds);
		var baotou = mjutils.checkBaoTou(holds);

		if (hasCaiShen && fanLen <= 0 && !baotou) {
			noNeedCheckHu = false;
		}
	}

	if (noNeedCheckHu) {
		//检查看是否可以和
		checkCanHu(game, turnSeat, pai);
	}

	//广播通知玩家出牌方
	turnSeat.canChuPai = true;
	userMgr.broacastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);

	//通知玩家做对应操作
	sendOperations(game, turnSeat, game.chuPai);
}

function getNumOfGangs(seatData) {
	return seatData.diangangs.length + seatData.wangangs.length + seatData.angangs.length;
}

function getNumOfPengs(seatData) {
	return seatData.pengs.length;
}

function calculateResult(game, roomInfo) {
	var renshu = game.conf.renshu;
	var baseScore = game.baseScore;
	var score = baseScore;
	var huScore = 0;

	if (game.state == "huang") {
		for (var i = 0; i < game.gameSeats.length; ++i) {
			var sd = game.gameSeats[i];
			sd.score = 0;
		}

		return;
	}

	//先计算基础分
	for (var i = 0; i < game.gameSeats.length; ++i) {
		var sd = game.gameSeats[i];
		if (sd.huInfo.length > 0) {
			var info = sd.huInfo[0];

			if (in_array(TYPE_GANG_KAI, sd.fanRecord)) {
				// 如果含杠开, 去掉前面那个杠
				var index = index_of(sd.fanRecord, TYPE_GANG);

				if (index > -1) {
					sd.fanRecord.splice(index, 1)
				}
			}

			// 查看手牌三张的个数
			var holds = sd.holds.concat();
			var holdsPengs = mjutils.pengNumInHolds(holds, 3);

			info.fan = sd.fanRecord.length;
			sd.fan = info.fan;
			sd.numOfGang = info.numofgang;
			sd.numOfPeng = info.numofpeng;

			var huBaseScore = baseScore + info.numofgang * 2 + info.numofpeng + holdsPengs;

			var huText = "";

			if (sd.pattern == "thirteenMess") {
				huBaseScore = 5;
				huText = "十三不搭";
			}

			if (sd.pattern == "qidui") {
				huBaseScore = 5;
				huText = "七对";
			}

			if (sd.pattern != "thirteenMess" && sd.pattern != "qidui") {
				if (info.fan <= 0) {
					huText = "平胡";
				}
			}

			if (sd.pattern != "thirteenMess") {

				if (info.fan > 0) {
					var huList = ["杠", "飘", "杠开", "爆头"];
					var huTextList = [];
					for (var i = 0; i < info.fan; i++) {
						huTextList.push(huList[sd.fanRecord[i] - 1])
					}

					huText = huTextList.join(" ");
				}
			}

			info.hutext = huText;

			score = computeFanScore(huBaseScore, info.fan);

			if (sd.pattern == "thirteenMess") {
				score = 5;
			}

			// 超过封顶的情况
			if (score > game.conf.fengding) {
				score = game.conf.fengding;
			}

			huScore = (renshu - 1) * score;

			break;
		}
	}

	//再计算其他玩家分
	for (var i = 0; i < game.gameSeats.length; ++i) {
		var sd = game.gameSeats[i];
		if (sd.huInfo.length > 0) {
			continue;
		}

		var gangs = sd.angangs.length + sd.diangangs.length + sd.wangangs.length;
		var pengs = sd.pengs.length;

		sd.score = gangs * 2 + pengs - score;
		huScore = huScore - (gangs * 2 + pengs);
	}

	//最后计算胡家分
	for (var i = 0; i < game.gameSeats.length; ++i) {
		var sd = game.gameSeats[i];
		if (sd.huInfo.length) {
			sd.score = huScore;
		}
	}
}

function computeFanScore(baseScore, fan) {
	return (1 << fan) * baseScore;
}

function doGameOver(game, renshu, userId, forceEnd) {
	var roomId = roomMgr.getUserRoom(userId);
	if (roomId == null) {
		return;
	}
	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo == null) {
		return;
	}

	var results = [];
	var dbresult = renshu > 2 ? [0, 0, 0, 0] : [0, 0];

	var fnNoticeResult = function (isEnd) {
		var endinfo = null;
		if (isEnd) {
			endinfo = [];
			for (var i = 0; i < roomInfo.seats.length; ++i) {
				var rs = roomInfo.seats[i];
				endinfo.push({
					numofgang: rs.numOfGang,
					numofpeng: rs.numOfPeng
				});
			}
		}

		userMgr.broacastInRoom('game_over_push', {results: results, endinfo: endinfo}, userId, true);
		//如果局数已够，则进行整体结算，并关闭房间
		if (isEnd) {
			setTimeout(function () {
				if (roomInfo.numOfGames > 1) {
					store_history(roomInfo);
				}
				userMgr.kickAllInRoom(roomId);
				roomMgr.destroy(roomId);
				db.archive_games(roomInfo.uuid);
			}, 1500);
		}
	};

	if (game != null) {
		if (!forceEnd) {
			calculateResult(game, roomInfo);
		}

		for (var i = 0; i < roomInfo.seats.length; ++i) {
			var rs = roomInfo.seats[i];
			var sd = game.gameSeats[i];

			rs.ready = false;
			rs.score += sd.score;
			rs.numOfGang += sd.numOfGang;
			rs.numOfPeng += sd.numOfPeng;
			var gangs = [];
			gangs = [].concat(sd.angangs).concat(sd.diangangs).concat(sd.wangangs);

			var userRT = {
				userId: sd.userId,
				actions: [],
				pengs: sd.pengs,
				gangs: gangs,
				holds: sd.holds,
				score: sd.score,
				totalscore: rs.score,
				huinfo: sd.huInfo
			};

			for (var k in sd.actions) {
				userRT.actions[k] = {
					type: sd.actions[k].type,
				};
			}
			results.push(userRT);

			dbresult[i] = sd.score;
			delete gameSeatsOfUsers[sd.userId];
		}
		delete games[roomId];

		var old = roomInfo.nextButton;
		roomInfo.nextButton = game.turn;

		if (old != roomInfo.nextButton) {
			db.update_next_button(roomId, roomInfo.nextButton);
		}
	}

	if (forceEnd || game == null) {
		fnNoticeResult(true);
	}
	else {
		//保存游戏
		store_game(game, function (ret) {
			db.update_game_result(roomInfo.uuid, game.gameIndex, dbresult);

			//记录玩家操作
			var str = JSON.stringify(game.actionList);
			db.update_game_action_records(roomInfo.uuid, game.gameIndex, str);

			//保存游戏局数
			db.update_num_of_turns(roomId, roomInfo.numOfGames);

			var isEnd = (roomInfo.numOfGames >= roomInfo.conf.jushu);
			fnNoticeResult(isEnd);
		});
	}
}

function recordGameAction(game, si, action, pai) {
	game.actionList.push(si);
	game.actionList.push(action);
	if (pai != null) {
		game.actionList.push(pai);
	}
}

exports.setReady = function (userId, callback) {
	var roomId = roomMgr.getUserRoom(userId);
	if (roomId == null) {
		return;
	}

	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo == null) {
		return;
	}

	roomMgr.setReady(userId, true);

	var game = games[roomId];
	var renshu = roomInfo.conf.renshu;

	if (game == null) {
		if (roomInfo.seats.length == renshu) {
			for (var i = 0; i < roomInfo.seats.length; ++i) {
				var s = roomInfo.seats[i];
				if (s.ready == false || userMgr.isOnline(s.userId) == false) {
					return;
				}
			}
			//人到齐了，并且都准备好了，则开始新的一局
			exports.begin(roomId);
		}
	}
	else {
		var numOfMJ = game.mahjongs.length - game.currentIndex;

		var data = {
			state: game.state,
			numofmj: numOfMJ,
			button: game.button,
			turn: game.turn,
			chuPai: game.chuPai,
		};

		data.seats = [];
		var seatData = null;
		for (var i = 0; i < renshu; ++i) {
			var sd = game.gameSeats[i];

			var s = {
				userid: sd.userId,
				folds: sd.folds,
				angangs: sd.angangs,
				diangangs: sd.diangangs,
				wangangs: sd.wangangs,
				pengs: sd.pengs,
				huinfo: sd.huInfo
			};

			if (sd.userId == userId) {
				s.holds = sd.holds;
				seatData = sd;
			}
			data.seats.push(s);
		}

		//同步整个信息给客户端
		userMgr.sendMsg(userId, 'game_sync_push', data);
		sendOperations(game, seatData, game.chuPai);
	}
};

function store_single_history(userId, history) {
	db.get_user_history(userId, function (data) {
		if (data == null) {
			data = [];
		}
		while (data.length >= 10) {
			data.shift();
		}
		data.push(history);
		db.update_user_history(userId, data);
	});
}

function store_history(roomInfo) {
	var shuren = roomInfo.conf.renshu;

	var seats = roomInfo.seats;
	var history = {
		uuid: roomInfo.uuid,
		id: roomInfo.id,
		time: roomInfo.createTime,
		seats: new Array(shuren)
	};

	for (var i = 0; i < seats.length; ++i) {
		var rs = seats[i];
		var hs = history.seats[i] = {};
		hs.userid = rs.userId;
		hs.name = crypto.toBase64(rs.name);
		hs.score = rs.score;
	}

	for (var i = 0; i < seats.length; ++i) {
		var s = seats[i];
		store_single_history(s.userId, history);
	}
}

function construct_game_base_info(game) {
	var renshu = game.conf.renshu;

	var baseInfo = {
		button: game.button,
		index: game.gameIndex,
		mahjongs: game.mahjongs,
		game_seats: new Array(renshu)
	};
	for (var i = 0; i < renshu; ++i) {
		baseInfo.game_seats[i] = game.gameSeats[i].holds;
	}
	game.baseInfoJson = JSON.stringify(baseInfo);
}

function store_game(game, callback) {
	db.create_game(game.roomInfo.uuid, game.gameIndex, game.baseInfoJson, callback);
}

//开始新的一局
exports.begin = function (roomId) {
	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo == null) {
		return;
	}
	var seats = roomInfo.seats;

	var renshu = roomInfo.conf.renshu;

	var game = {
		conf: roomInfo.conf,
		roomInfo: roomInfo,
		gameIndex: roomInfo.numOfGames,

		button: roomInfo.nextButton,
		mahjongs: renshu > 2 ? new Array(136) : new Array(100),
		currentIndex: 0,
		gameSeats: new Array(renshu),

		turn: 0,
		chuPai: -1,
		state: "idle",
		actionList: [],
		chupaiCnt: 0,
	};

	roomInfo.numOfGames++;

	for (var i = 0; i < renshu; ++i) {
		var data = game.gameSeats[i] = {};

		data.game = game;
		game.baseScore = 4;

		data.seatIndex = i;

		data.userId = seats[i].userId;
		//持有的牌
		data.holds = [];
		//打出的牌
		data.folds = [];
		//暗杠的牌
		data.angangs = [];
		//点杠的牌
		data.diangangs = [];
		//弯杠的牌
		data.wangangs = [];
		//碰了的牌
		data.pengs = [];

		//玩家手上的牌的数目，用于快速判定碰杠
		data.countMap = {};
		//玩家听牌，用于快速判定胡了的番数
		data.tingList = [];
		//胡牌模式
		data.patternList = [];

		//是否可以杠
		data.canGang = false;
		//是否正在飘财, 用于飘财后摸张打张
		data.isPiaoCai = false;
		//是否可以爆头
		data.canBaoTou = false;
		//是否可以胡
		data.canHu = false;
		//胡的模式
		data.pattern = "normal";
		//用于记录玩家可以杠的牌
		data.gangPai = [];

		//是否可以碰
		data.canPeng = false;
		//是否可以出牌
		data.canChuPai = false;

		data.actions = [];

		//是否是自摸
		data.fan = 0;
		data.score = 0;
		//番记录
		data.fanRecord = [];
		data.huInfo = [];

		//统计信息
		data.numOfGang = 0;
		data.numOfPeng = 0;

		gameSeatsOfUsers[data.userId] = data;
	}

	games[roomId] = game;
	//洗牌
	shuffle(game);
	//发牌
	deal(game);

	var numOfMJ = game.mahjongs.length - game.currentIndex;

	for (var i = 0; i < seats.length; ++i) {
		//开局时，通知前端必要的数据
		var s = seats[i];
		//通知玩家手牌
		userMgr.sendMsg(s.userId, 'game_holds_push', game.gameSeats[i].holds);
		//通知还剩多少张牌
		userMgr.sendMsg(s.userId, 'mj_count_push', numOfMJ);
		//通知还剩多少局
		userMgr.sendMsg(s.userId, 'game_num_push', roomInfo.numOfGames);
		//通知游戏开始
		userMgr.sendMsg(s.userId, 'game_begin_push', game.button);
	}

	// 通知庄家发牌
	construct_game_base_info(game);

	for (var i = 0; i < seats.length; ++i) {
		var s = seats[i];
		userMgr.broacastInRoom('game_playing_push', null, s.userId, true);
	}

	//进行听牌检查
	for (var i = 0; i < game.gameSeats.length; ++i) {
		var duoyu = -1;
		var gs = game.gameSeats[i];
		if (gs.holds.length == 14) {
			duoyu = gs.holds.pop();
			gs.countMap[duoyu] -= 1;
			game.turn = i;
		}
		checkCanTingPai(game, gs);
		if (duoyu >= 0) {
			gs.holds.push(duoyu);
			gs.countMap[duoyu]++;
		}
	}

	var turnSeat = game.gameSeats[game.turn];
	game.state = "playing";
	//通知玩家出牌方
	turnSeat.canChuPai = true;
	userMgr.broacastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);
	//检查是否可以暗杠或者胡
	//直杠
	checkCanAnGang(game, turnSeat);
	//检查胡 用最后一张来检查
	checkCanHu(game, turnSeat, turnSeat.holds[turnSeat.holds.length - 1]);
	//通知前端
	sendOperations(game, turnSeat, game.chuPai);
};

exports.chuPai = function (userId, pai) {
	pai = Number.parseInt(pai);
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		console.log("can't find user game data.");
		return;
	}

	var game = seatData.game;
	var seatIndex = seatData.seatIndex;
	//如果不该他出，则忽略
	if (game.turn != seatIndex) {
		console.log("not your turn.");
		return;
	}

	if (!seatData.canChuPai) {
		console.log('no need chupai.');
		return;
	}

	if (hasOperations(seatData)) {
		console.log('plz guo before you chupai.');
		return;
	}

	//如果有人飘财而且飘财后摸张打张，则只能打最后一张牌
	var piaocai = game.conf.piaocai;
	var isPiaoCai = false;
	if (piaocai <= 1) {
		//摸张打张
		for (var i = 0; i < game.gameSeats.length; ++i) {
			var sd = game.gameSeats[i];

			if (game.turn == i) {
				if (sd.isPiaoCai) {
					sd.isPiaoCai = false;
				}
				continue;
			}

			if (sd.isPiaoCai) {
				isPiaoCai = true;
				break;
			}
		}
	}

	if (isPiaoCai) {
		if (seatData.holds[seatData.holds.length - 1] != pai) {
			console.log('only deal last one when hued.');
			return;
		}
	}

	// 清除不是杠的番
	//var fanRecord = seatData.fanRecord || [];
	//if (fanRecord.length > 0) {
	//	for (var i = 0; i < fanRecord.length; i++) {
	//		if (fanRecord[i] != TYPE_PIAO) {
	//			fanRecord.splice(i, 1)
	//		}
	//	}
	//}

	//从此人牌中扣除
	var index = seatData.holds.indexOf(pai);
	if (index == -1) {
		console.log("holds:" + seatData.holds);
		console.log("can't find mj." + pai);
		return;
	}

	seatData.canChuPai = false;
	game.chupaiCnt++;

	seatData.holds.splice(index, 1);
	seatData.countMap[pai]--;
	game.chuPai = pai;
	recordGameAction(game, seatData.seatIndex, ACTION_CHUPAI, pai);
	checkCanTingPai(game, seatData);

	//检查是不是飘财
	if (seatData.tingList.length > 0) {
		var holds = JSON.parse(JSON.stringify(seatData.holds));

		if (holds.length > 0) {
			holds = inMJUtils(holds);
		}

		seatData.canBaoTou = !!mjutils.checkBaoTou(holds);

		if(seatData.isPiaoCai && !(pai == 33 && seatData.canBaoTou)) {
			// 如果上次自己就是飘, 本次不是飘, 清除番
			seatData.fanRecord = [];

			//var fanRecord2 = seatData.fanRecord || [];
			//if (fanRecord2.length > 0) {
			//	for (var i = 0; i < fanRecord2.length; i++) {
			//		if (fanRecord2[i] == TYPE_PIAO) {
			//			fanRecord2.splice(i, 1)
			//		}
			//	}
			//}
		} else if (pai == 33 && seatData.canBaoTou) {
			seatData.fanRecord.push(TYPE_PIAO);
			seatData.isPiaoCai = true;
		} else {
			seatData.fanRecord = [];
			seatData.isPiaoCai = false;
		}
	} else {
		// 没听牌, 清除fanRecord
		seatData.fanRecord = [];
	}

	userMgr.broacastInRoom('game_chupai_notify_push', {
		userId: seatData.userId,
		pai: pai,
		isPiaoCai: seatData.isPiaoCai
	}, seatData.userId, true);

	//检查是否有人要碰 要杠
	var hasActions = false;
	for (var i = 0; i < game.gameSeats.length; ++i) {
		//玩家自己不检查
		if (game.turn != i) {
			var ddd = game.gameSeats[i];
			// 如果没有飘财，检查杠和碰
			if (!in_array(ddd.fanRecord, TYPE_PIAO)) {
				// 检查杠和碰
				if (!isPiaoCai) {
					checkCanPeng(game, ddd, pai);
					checkCanDianGang(game, ddd, pai);
				}

				if (hasOperations(ddd)) {
					sendOperations(game, ddd, game.chuPai);
					hasActions = true;
				}
			}
		}
	}

	//如果没有人有操作，则向下一家发牌，并通知他出牌
	if (!hasActions) {
		setTimeout(function () {
			userMgr.broacastInRoom('guo_notify_push', {
				userId: seatData.userId,
				pai: game.chuPai
			}, seatData.userId, true);
			seatData.folds.push(game.chuPai);
			game.chuPai = -1;
			moveToNextUser(game);
			doUserMoPai(game);
		}, 500);
	}
};

exports.peng = function (userId) {
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		console.log("can't find user game data.");
		return;
	}

	var game = seatData.game;

	//如果是他出的牌，则忽略
	if (game.turn == seatData.seatIndex) {
		console.log("it's your turn.");
		return;
	}

	//如果没有碰的机会，则不能再碰
	if (seatData.canPeng == false) {
		console.log("seatData.peng == false");
		return;
	}

	clearAllOptions(game);

	//验证手上的牌的数目
	var pai = game.chuPai;
	var c = seatData.countMap[pai];
	if (c == null || c < 2) {
		console.log("pai:" + pai + ",count:" + c);
		console.log(seatData.holds);
		console.log("lack of mj.");
		return;
	}

	//进行碰牌处理, 扣掉手上的牌, 从此人牌中扣除
	for (var i = 0; i < 2; ++i) {
		var index = seatData.holds.indexOf(pai);
		if (index == -1) {
			console.log("can't find mj.");
			return;
		}
		seatData.holds.splice(index, 1);
		seatData.countMap[pai]--;
	}
	seatData.pengs.push(pai);
	game.chuPai = -1;

	recordGameAction(game, seatData.seatIndex, ACTION_PENG, pai);

	//广播通知其它玩家
	userMgr.broacastInRoom('peng_notify_push', {userid: seatData.userId, pai: pai}, seatData.userId, true);

	//碰的玩家打牌
	moveToNextUser(game, seatData.seatIndex);

	//广播通知玩家出牌方
	seatData.canChuPai = true;
	userMgr.broacastInRoom('game_chupai_push', seatData.userId, seatData.userId, true);
};

exports.isPlaying = function (userId) {
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		return false;
	}

	var game = seatData.game;

	if (game.state == "idle") {
		return false;
	}
	return true;
};

function doGang(game, seatData, gangtype, numOfCnt, pai) {
	var seatIndex = seatData.seatIndex;

	if (gangtype == "wangang") {
		var idx = seatData.pengs.indexOf(pai);
		if (idx >= 0) {
			seatData.pengs.splice(idx, 1);
		}
	}

	//进行碰牌处理，扣掉手上的牌，从此人牌中扣除
	for (var i = 0; i < numOfCnt; ++i) {
		var index = seatData.holds.indexOf(pai);
		if (index == -1) {
			console.log(seatData.holds);
			console.log("can't find mj.");
			return;
		}
		seatData.holds.splice(index, 1);
		seatData.countMap[pai]--;
	}

	recordGameAction(game, seatData.seatIndex, ACTION_GANG, pai);

	//记录下玩家的杠牌
	if (gangtype == "angang") {
		seatData.angangs.push(pai);
	} else if (gangtype == "diangang") {
		seatData.diangangs.push(pai);
	} else if (gangtype == "wangang") {
		seatData.wangangs.push(pai);
	} else {
		console.log("error gang type");
		return;
	}

	//记录杠番
	seatData.fanRecord.push(TYPE_GANG);

	checkCanTingPai(game, seatData);
	//通知其他玩家，有人杠了牌
	userMgr.broacastInRoom('gang_notify_push', {
		userid: seatData.userId,
		pai: pai,
		gangtype: gangtype
	}, seatData.userId, true);

	//变成自己的轮子
	moveToNextUser(game, seatIndex);
	//再次摸牌
	doUserMoPai(game);
}

exports.gang = function (userId, pai) {
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		console.log("can't find user game data.");
		return;
	}

	var seatIndex = seatData.seatIndex;
	var game = seatData.game;

	//如果没有杠的机会，则不能再杠
	if (!seatData.canGang) {
		return;
	}

	var numOfCnt = seatData.countMap[pai];

	if (seatData.gangPai.indexOf(pai) == -1) {
		console.log("the given pai can't be ganged.");
		return;
	}

	var gangtype = "";
	//弯杠 去掉碰牌
	if (numOfCnt == 1) {
		gangtype = "wangang"
	} else if (numOfCnt == 3) {
		gangtype = "diangang"
	} else if (numOfCnt == 4) {
		gangtype = "angang";
	} else {
		console.log("invalid pai count.");
		return;
	}

	game.chuPai = -1;
	clearAllOptions(game);
	game.canGang = false;
	seatData.canChuPai = false;

	userMgr.broacastInRoom('hangang_notify_push', seatIndex, seatData.userId, true);

	//var turnSeat = game.gameSeats[game.turn];
	doGang(game, seatData, gangtype, numOfCnt, pai);
};

exports.hu = function (userId) {
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		console.log("can't find user game data.");
		return;
	}

	var seatIndex = seatData.seatIndex;
	var game = seatData.game;

	//如果他不能和牌，那和个啥啊
	if (!seatData.canHu) {
		console.log("invalid request.");
		return;
	}

	var fanRecord = seatData.fanRecord;
	if (fanRecord.length > 0 && fanRecord[fanRecord.length - 1] === TYPE_GANG) {
		seatData.fanRecord.push(TYPE_GANG_KAI);
	}

	if (seatData.canBaoTou) {
		seatData.fanRecord.push(TYPE_BAOTOU)
	}

	//标记为和牌
	var hupai = game.chuPai;

	var huData = {
		ishupai: true,
		pai: -1
	};

	huData.numofgang = getNumOfGangs(seatData);
	huData.numofpeng = getNumOfPengs(seatData);

	seatData.huInfo.push(huData);

	if (game.chuPai == -1) {
		hupai = seatData.holds.pop();
		seatData.countMap[hupai]--;
		huData.pai = hupai;

		recordGameAction(game, seatIndex, ACTION_HU, hupai);
	}

	clearAllOptions(game, seatData);
	//通知前端，有人和牌了
	userMgr.broacastInRoom('hu_push', {seatindex: seatIndex, hupai: hupai}, seatData.userId, true);
	//结束比赛
	doGameOver(game, seatData.game ? seatData.game.conf.renshu : 4, userId);
};

exports.guo = function (userId) {
	var seatData = gameSeatsOfUsers[userId];
	if (seatData == null) {
		console.log("can't find user game data.");
		return;
	}

	var seatIndex = seatData.seatIndex;
	var game = seatData.game;

	//如果玩家没有对应的操作，则也认为是非法消息
	if ((seatData.canGang || seatData.canPeng || seatData.canHu) == false) {
		console.log("no need guo.");
		return;
	}

	if (seatData.canHu) {
		seatData.canChuPai = true;
	}

	//如果是玩家自己的轮子，不是接牌，则不需要额外操作
	var doNothing = game.chuPai == -1 && game.turn == seatIndex;

	userMgr.sendMsg(seatData.userId, "guo_result");
	clearAllOptions(game, seatData);

	if (doNothing) {
		return;
	}

	//如果还有人可以操作，则等待
	for (var i = 0; i < game.gameSeats.length; ++i) {
		var ddd = game.gameSeats[i];
		if (hasOperations(ddd)) {
			return;
		}
	}

	//如果是已打出的牌，则需要通知。
	if (game.chuPai >= 0) {
		var uid = game.gameSeats[game.turn].userId;
		userMgr.broacastInRoom('guo_notify_push', {userId: uid, pai: game.chuPai}, seatData.userId, true);
		seatData.folds.push(game.chuPai);
		game.chuPai = -1;
	}

	//清除所有的操作
	clearAllOptions(game);

	//下家摸牌
	moveToNextUser(game);
	doUserMoPai(game);
};

exports.hasBegan = function (roomId) {
	var game = games[roomId];
	if (game != null) {
		return true;
	}
	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo != null) {
		return roomInfo.numOfGames > 0;
	}
	return false;
};


var dissolvingList = [];

exports.doDissolve = function (roomId) {
	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo == null) {
		return null;
	}

	var game = games[roomId];
	var renshu = roomInfo.conf.renshu;
	doGameOver(game, renshu, roomInfo.seats[0].userId, true);
};

exports.dissolveRequest = function (roomId, userId) {
	var roomInfo = roomMgr.getRoom(roomId);
	var renshu = roomInfo.conf.renshu;

	if (roomInfo == null) {
		return null;
	}

	if (roomInfo.dr != null) {
		return null;
	}

	var seatIndex = roomMgr.getUserSeat(userId);
	if (seatIndex == null) {
		return null;
	}

	roomInfo.dr = {
		endTime: Date.now() + 30000,
		states: renshu > 2 ? [false, false, false, false] : [false, false]
	};
	roomInfo.dr.states[seatIndex] = true;

	dissolvingList.push(roomId);

	return roomInfo;
};

exports.dissolveAgree = function (roomId, userId, agree) {
	var roomInfo = roomMgr.getRoom(roomId);
	if (roomInfo == null) {
		return null;
	}

	if (roomInfo.dr == null) {
		return null;
	}

	var seatIndex = roomMgr.getUserSeat(userId);
	if (seatIndex == null) {
		return null;
	}

	if (agree) {
		roomInfo.dr.states[seatIndex] = true;
	}
	else {
		roomInfo.dr = null;
		var idx = dissolvingList.indexOf(roomId);
		if (idx != -1) {
			dissolvingList.splice(idx, 1);
		}
	}
	return roomInfo;
};

function update() {
	for (var i = dissolvingList.length - 1; i >= 0; --i) {
		var roomId = dissolvingList[i];

		var roomInfo = roomMgr.getRoom(roomId);
		if (roomInfo != null && roomInfo.dr != null) {
			if (Date.now() > roomInfo.dr.endTime) {
				console.log("delete room and games");
				exports.doDissolve(roomId);
				dissolvingList.splice(i, 1);
			}
		}
		else {
			dissolvingList.splice(i, 1);
		}
	}
}

setInterval(update, 1000);